/* slide.c
 */

/*
    Slide, a Sliding Puzzle Game for the Psion Series 3
    Version 1.0a
    Copyright (C) 1993  J Cade Roux

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 * See the file COPYING for the GNU General Public License.
 * Mail: Cade Roux
 *                      c/o Dubroca
 *                      Box 513
 *                      Boutte, LA  70039
 *                      USA
 */

#include <utils.h>

#include "slide.h"
#include "global.h"

LOCAL_D H_MENU_DATA		mdata[] = {	"Slide", 4,
												NULL } ;

LOCAL_D TEXT *				cmds[] = {	"oOptions",
												"sShow Solved",
												"nNew Game",
												"xExit",
												NULL } ;

GLREF_D WSERV_SPEC *		wserv_channel ;
GLREF_D TEXT *				DatCommandPtr ;
GLDEF_D TEXT **			_cmds = cmds ;
GLDEF_D H_MENU_DATA *	_mdata = mdata ;

#define MIN_GRID_SIZE			4
#define MAX_GRID_SIZE			6
#define MIX_COUNT					50
#define SPACING					10
#define PUZZLE_TYPE_NUM			1
#define PUZZLE_TYPE_PIC			2

/* Global data defining the puzzle */
LOCAL_D struct {
	UWORD			type ;							/* puzzle type */
	UWORD			grid_size ;						/* pieces on a side */
	TEXT			bit_name[P_FNAMESIZE] ;		/* name of bitmap */
	BYTE *		pieces ;							/* array containing piece numbers */
	INT			curr_pos ;						/* current position of blank */
} puzzle = {	PUZZLE_TYPE_NUM,
					4,
					"*.pic",
					NULL,
					15 } ;

/* global data containing graphics data */
LOCAL_D struct {
	UINT		win ;						/* main window id                      */
} gfx ;

/* global data containing graphics metrics */
LOCAL_D struct {
	WORD		board_size ;			/* size in pixels of board     */
	WORD		piece_size ;			/* size in pixels of one piece */
} metrics ;

/* global data containing the game status */
LOCAL_D struct {
	ULONG			seedl ;				/* random number seed               */
	BOOL			quit ;				/* player requested quit            */
	BOOL			new_game ;			/* player requested a new game      */
	INT			moves ;				/* number of move in the game       */
} status = { 0L, FALSE, FALSE } ;

#define POS(x,y)			((x) + (y) * puzzle.grid_size)
#define ROW(piece)		((piece) / puzzle.grid_size)
#define COL(piece)		((piece) % puzzle.grid_size)
#define IN_GRID(x,y)		(	(x) >= 0 &&\
									(y) >= 0 &&\
									(x) < puzzle.grid_size &&\
									(y) < puzzle.grid_size )
#define PIECE(x,y)		(puzzle.pieces[POS((x),(y))])

/* reset the board to the solved position */
LOCAL_C VOID ResetBoard(VOID) {
	INT		iLp ;

	for ( iLp = 0 ; iLp < puzzle.grid_size * puzzle.grid_size - 1 ; iLp++ )
		puzzle.pieces[iLp] = iLp + 1 ;

	puzzle.pieces[iLp] = 0 ;
	puzzle.curr_pos = iLp ;
}

/* redraw the whole board */
LOCAL_C VOID DrawBoard(BOOL show) {
	P_RECT		src_rect = { {0, 0} } ;
	P_RECT		dst_rect = { {0, 0} } ;
	INT			iLp ;
	INT			bmp ;

	src_rect.br.x = src_rect.br.y = metrics.board_size ;
	gClrRect(&src_rect, G_TRMODE_CLR) ;

	if ( puzzle.type == PUZZLE_TYPE_PIC ) {
		WMSG_KEY		key ;

		bmp = gOpenBit(puzzle.bit_name, 0, WS_BIT_SEG, &dst_rect.br) ;
		if ( show ) {
			InfoMsg(RS_PRESS) ;
			gCopyBit(&dst_rect.tl, bmp, &src_rect, G_TRMODE_REPL) ;
			uGetKey(&key) ;
		}
	}

	for ( iLp = 0 ; iLp < puzzle.grid_size * puzzle.grid_size ; iLp++ ) {
		dst_rect.tl.x = COL(iLp) * metrics.piece_size ;
		dst_rect.tl.y = ROW(iLp) * metrics.piece_size ;
		dst_rect.br.x = dst_rect.tl.x + metrics.piece_size ;
		dst_rect.br.y = dst_rect.tl.y + metrics.piece_size ;

		if ( puzzle.pieces[iLp] > 0 ) {
			if ( puzzle.type == PUZZLE_TYPE_PIC ) {
				src_rect.tl.x = COL(puzzle.pieces[iLp] - 1) * metrics.piece_size ;
				src_rect.tl.y = ROW(puzzle.pieces[iLp] - 1) * metrics.piece_size ;
				src_rect.br.x = src_rect.tl.x + metrics.piece_size ;
				src_rect.br.y = src_rect.tl.y + metrics.piece_size ;
				gCopyBit(&dst_rect.tl, bmp, &src_rect, G_TRMODE_REPL) ;
			}
			else {
				TEXT		str[3] ;

				str[p_itob(str, puzzle.pieces[iLp])] = 0 ;
				gBorderRect(&dst_rect, W_BORD_CORNER_1) ;
				p_insrec(&dst_rect, 1, 1) ;
				gPrintBoxText(&dst_rect, metrics.piece_size / 2 + 3, G_TEXT_ALIGN_CENTRE, 0, str, p_slen(str)) ;
			}
		}
		else
			gClrRect(&dst_rect, G_TRMODE_CLR) ;

	}

	if ( puzzle.type == PUZZLE_TYPE_PIC )
		wFree(bmp) ;
}

/* is the puzzle solved */
LOCAL_C BOOL Solved(VOID) {
	INT		iLp ;

	for ( iLp = 0 ; iLp < puzzle.grid_size * puzzle.grid_size - 1 ; iLp++ )
		if ( puzzle.pieces[iLp] != iLp + 1 )
			return(FALSE) ;

	return(TRUE) ;
}

LOCAL_C VOID UpdateSize(VOID) {
	metrics.piece_size = metrics.board_size / puzzle.grid_size ;
	puzzle.pieces = f_realloc(puzzle.pieces, puzzle.grid_size * puzzle.grid_size * sizeof(BYTE)) ;
}

/* edit the play options */
LOCAL_C VOID Options(VOID) {
	H_DI_FSEL		fsel = { NULL, H_FILE_RESTRICT_LIST } ;
	UWORD				otype ;
	UWORD				osize ;
	H_DI_CHOICE		ch ;
	INT				iLp ;
	TEXT				str[8] ;
	INT				ret ;

	otype = puzzle.type ;
	osize = puzzle.grid_size ;

	Res_OpenDialog(RS_OPTIONS) ;
	Res_AddChoiceList(	RS_PTYPE,
								RS_NUM,
								RS_PIC,
								&puzzle.type) ;

	f_leave( uBeginDCL(&ch) ) ;
	for ( iLp = MIN_GRID_SIZE ; iLp <= MAX_GRID_SIZE ; iLp++ ) {
		p_atos(str, "%d x %d", iLp, iLp) ;
		f_leave( uGrowDCL(&ch, str) ) ;
	}
	puzzle.grid_size -= (MIN_GRID_SIZE - 1) ;
	Res_AddDCL(RS_SIZE, &puzzle.grid_size, &ch) ;

	ret = Res_RunDialog() ;
	puzzle.grid_size += (MIN_GRID_SIZE - 1) ;

	if ( !ret ) {
		puzzle.type = PUZZLE_TYPE_NUM ;
		return ;
	}

	if ( puzzle.type == PUZZLE_TYPE_PIC ) {
		fsel.fname = ZTStoBCS(puzzle.bit_name) ;
		Res_OpenDialog(RS_FILE) ;
		Res_AddDialogItem(	H_DIALOG_FSEL,
									RS_FNAME,
									&fsel) ;
		if ( !Res_RunDialog() )
			puzzle.type = PUZZLE_TYPE_NUM ;

		BCStoZTS(puzzle.bit_name) ;
	}

	if ( puzzle.type == PUZZLE_TYPE_PIC ) {
		P_POINT		size ;

		wFree(gOpenBit(puzzle.bit_name, 0, WS_BIT_SEG, &size)) ;
		if ( size.x < metrics.board_size || size.y < metrics.board_size ) {
			puzzle.type = PUZZLE_TYPE_NUM ;
			InfoMsg(RS_TOO_SMALL) ;
		}
	}

	if ( osize != puzzle.grid_size ) {
		status.new_game = TRUE ;
		UpdateSize() ;
	}
	else if ( otype != puzzle.type )
		DrawBoard(TRUE) ;
}

/* Execute menu command */
LOCAL_C VOID ExecuteCommand(INT keycode) {
	if ( (keycode = uLocateCommand(keycode)) < 0 )
		return ;

	switch(keycode) {

		case 0 : /* Options */
			Options() ;
			break ;

		case 1 : /* Show solved position */
			if ( puzzle.type == PUZZLE_TYPE_PIC )
				DrawBoard(TRUE) ;
			break ;

		case 2 : /* New Game */
			Check( p_enter5(Query, RS_NEW_GAME, RS_YES, RS_NO, &status.new_game) ) ;
			break ;

		case 3 : /* Exit */
			Check( p_enter5(Query, RS_QUIT, RS_YES, RS_NO, &status.quit) ) ;
			break ;

		default :
			break ;
	}
}

enum {	DIR_UP = 0,
			DIR_RIGHT,
			DIR_DOWN,
			DIR_LEFT,
			N_DIRS } ;

LOCAL_D P_POINT		vectors[] = {	{  0, -1},			/* Up */
												{  1,  0},			/* Right */
												{  0,  1},			/* Down */
												{ -1,  0} } ;		/* Left */

/* update the display of moves taken */
LOCAL_C VOID DrawMoves(INT moves) {
	P_RECT	rect = { {0, 0}, {60, 10} } ;
	TEXT		str[10] ;

	p_offrec(&rect, puzzle.grid_size * metrics.piece_size + SPACING, 0) ;

	if ( moves == 0 ) {
		TEXT *	fstr = NULL ;

		fstr = ReadResource(RS_MOVES, &fstr) ;
		gPrintBoxText(&rect, 8, G_TEXT_ALIGN_LEFT, 0, fstr, p_slen(fstr)) ;
		p_free(fstr) ;
	}

	p_offrec(&rect, 60, 0) ;
	str[p_itob(str, moves)] = 0 ;
	gPrintBoxText(&rect, 8, G_TEXT_ALIGN_LEFT, 0, str, p_slen(str)) ;
}

/* move in the direction specified */
LOCAL_C VOID MovePiece(INT dir) {
	INT			x ;
	INT			y ;
	INT			newx ;
	INT			newy ;
	P_RECT		rect = { {0, 0} } ;
	P_POINT		pos ;

	rect.br.x = rect.br.y = metrics.piece_size ;
	x = COL(puzzle.curr_pos) ;
	y = ROW(puzzle.curr_pos) ;
	newx = x + vectors[dir].x ;
	newy = y + vectors[dir].y ;
	if ( IN_GRID(newx, newy) ) {
		PIECE(x, y) = PIECE(newx, newy) ;
		PIECE(newx, newy) = 0 ;
		puzzle.curr_pos = POS(newx, newy) ;

		p_offrec(&rect, newx * metrics.piece_size, newy * metrics.piece_size) ;
		pos.x = -vectors[dir].x * metrics.piece_size ;
		pos.y = -vectors[dir].y * metrics.piece_size ;
		wScrollRect(gfx.win, &rect, &pos) ;
		gClrRect(&rect, G_TRMODE_CLR) ;

		DrawMoves(++status.moves) ;
	}
}

/* shuffle the pieces */
LOCAL_C VOID Shuffle(VOID) {
	INT		count ;
	INT		last_blank ;

	last_blank = puzzle.curr_pos ;
	status.seedl = p_date() ;

	/* Move the pieces MIX_COUNT times */
	for ( count = 0 ; count < MIX_COUNT ; count++ ) {
		INT		x ;
		INT		y ;
		INT		newx ;
		INT		newy ;
		INT		num_free ;
		INT		dir ;
		INT		iLp ;
		INT		jLp ;

		/* Calculate the number of free spaces */
		for (	x = COL(puzzle.curr_pos),
					y = ROW(puzzle.curr_pos),
					num_free = 0,
					iLp = 0 ;
				iLp < N_DIRS ;
				iLp++ ) {
			num_free += IN_GRID(x + vectors[iLp].x, y + vectors[iLp].y) ;
		}

		if ( count )
			num_free-- ;	/* Can't use the last move */

		/* Choose a direction */
		dir = Random(&status.seedl, 1, num_free) ;

		/* Count round clockwise from up to the dir-th free piece */
		x = COL(puzzle.curr_pos) ;
		y = ROW(puzzle.curr_pos) ;
		for (	iLp = jLp = 0 ;
				jLp < N_DIRS ;
				jLp++ )
			if (	IN_GRID(x + vectors[jLp].x, y + vectors[jLp].y) &&
					POS(x + vectors[jLp].x, y + vectors[jLp].y) != last_blank )
				if ( ++iLp == dir ) {
					newx = x + vectors[jLp].x ;
					newy = y + vectors[jLp].y ;
					PIECE(x, y) = PIECE(newx, newy) ;
					PIECE(newx, newy) = 0 ;
					puzzle.curr_pos = POS(newx, newy) ;
					last_blank = POS(x, y) ;
					break ;
				}

	}
}

#pragma save, ENTER_CALL

/* Play a single game */
LOCAL_C INT Play(VOID) {
	WMSG_KEY		key ;
	INT			ret ;

	ResetBoard() ;
	Shuffle() ;
	DrawBoard(TRUE) ;
	DrawMoves(status.moves = 0) ;
	status.new_game = FALSE ;

	do {

		uGetKey(&key) ;

		if ( key.keycode == CONS_EVENT_COMMAND )
			status.quit = TRUE ;
		else if ( key.keycode & W_SPECIAL_KEY )
			ExecuteCommand(key.keycode & ~W_SPECIAL_KEY) ;
		else switch( key.keycode ) {

			case W_KEY_HELP :
				if ( key.modifiers == W_CTRL_MODIFIER )
					SystemHelp() ;
				else
					Help(RH_MAIN, RH_MAIN) ;
				break ;

			case W_KEY_MENU :
				if ( (ret = uPresentMenus()) > 0 )
					ExecuteCommand(ret) ;
				break ;

			case W_KEY_RIGHT :
				MovePiece(DIR_LEFT) ;
				break ;

			case W_KEY_LEFT :
				MovePiece(DIR_RIGHT) ;
				break ;

			case W_KEY_UP :
				MovePiece(DIR_DOWN) ;
				break ;

			case W_KEY_DOWN :
				MovePiece(DIR_UP) ;
				break ;

			default :
				break ;
		}

		if ( status.quit || status.new_game )
			break ;

		if (	puzzle.curr_pos == puzzle.grid_size * puzzle.grid_size - 1 &&
				Solved() ) {
			Check( p_enter5(Query, RS_NEW_GAME, RS_YES, RS_NO, &status.new_game) ) ;
			status.quit = !status.new_game ;
		}

	} while ( !(status.quit || status.new_game) ) ;

	return(E_APP_NONE) ;
}

LOCAL_C INT SpecificInit(VOID) {
	hCrackCommandLine() ;
	p_unmarka() ;

	OpenResource(DatCommandPtr) ;

	/* Gfx init */
	gCreateGC0(gfx.win = uFindMainWid()) ;
	metrics.board_size = MIN(	wserv_channel->conn.info.pixels.x,
										wserv_channel->conn.info.pixels.y ) ;

	UpdateSize() ;

	/* About message */
	Help(RH_ABOUT, RH_MAIN) ;

	return(E_APP_NONE) ;
}

#pragma restore

GLDEF_C VOID main(VOID) {
	uCommonInit() ;
	wDisableLeaves(FALSE) ;
	Check( p_enter1(SpecificInit) ) ;
	do {
		Check( p_enter1(Play) ) ;
	} while ( !status.quit ) ;
	p_exit(E_APP_NONE) ;
}

